// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d10.h>
#include <d3dx10.h>

// global declarations
ID3D10Device* device;    // the pointer to our Direct3D device interface
ID3D10RenderTargetView* rtv;    // the pointer to the render target view
IDXGISwapChain* swapchain;    // the pointer to the swap chain class

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow){
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.lpszClassName = "WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL,
                          "WindowClass",
                          "Our First Direct3D Program",
                          WS_OVERLAPPEDWINDOW,
                          300, 300,
                          800, 600,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        if(msg.message == WM_QUIT)
            break;

        render_frame();
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}

// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    DXGI_SWAP_CHAIN_DESC scd;    // create a struct to hold various swap chain information

    ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));    // clear out the struct for use

    scd.BufferCount = 1;    // create two buffers, one for the front, one for the back
    scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;    // use 32-bit color
    scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;    // tell how the chain is to be used
    scd.OutputWindow = hWnd;    // set the window to be used by Direct3D
    scd.SampleDesc.Count = 1;    // set the level of multi-sampling
    scd.SampleDesc.Quality = 0;    // set the quality of multi-sampling
    scd.Windowed = TRUE;    // set to windowed or full-screen mode

    // create a device class and swap chain class using the information in the scd struct
    D3D10CreateDeviceAndSwapChain(NULL,
                                  D3D10_DRIVER_TYPE_HARDWARE,
                                  NULL,
                                  0,
                                  D3D10_SDK_VERSION,
                                  &scd,
                                  &swapchain,
                                  &device);

    // get the address of the back buffer and use it to create the render target
    ID3D10Texture2D* pBackBuffer;
    swapchain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&pBackBuffer);
    device->CreateRenderTargetView(pBackBuffer, NULL, &rtv);
    pBackBuffer->Release();

    // set the render target as the back buffer
    device->OMSetRenderTargets(1, &rtv, NULL);

    D3D10_VIEWPORT viewport;    // create a struct to hold the viewport data

    ZeroMemory(&viewport, sizeof(D3D10_VIEWPORT));    // clear out the struct for use

    viewport.TopLeftX = 0;    // set the left to 0
    viewport.TopLeftY = 0;    // set the top to 0
    viewport.Width = 800;    // set the width to the window's width
    viewport.Height = 600;    // set the height to the window's height

    device->RSSetViewports(1, &viewport);    // set the viewport
}

// this is the function used to render a single frame
void render_frame(void)
{
    // clear the window to a deep blue
    device->ClearRenderTargetView(rtv, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));

    // do 3D rendering on the back buffer here

    // display the rendered frame
    swapchain->Present(0, 0);
}

// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    swapchain->Release();    // close and release the swap chain
    rtv->Release();    // close and release the render target view
    device->Release();    // close and release the 3D device
}